const DefaultDataAdapter = require('./default-data-adapter');
const { resultKind, testKind, coverageKind } = require('../../../tests');
const { statusKind } = require('../../../files');
const { versionType } = require('../../../project-versions');
const funcsModel = require('../../../functions');
const common = require('../../../common');
const utility = require('../../../utility');

class TargetMachineDataAdapter extends DefaultDataAdapter {
  constructor(files, functions, tests, projectVersion, project) {
    super();
    this.files = files;
    this.functions = functions;
    this.tests = tests;
    this.version = projectVersion;
    this.project = project;
  }

  async getFiles() {
    return Promise.resolve([...this.files.values()]);
  }

  async getFunctions(versionId, fileIds = undefined) {
    if (fileIds) {
      if (!utility.isArrayType(fileIds)) {
        fileIds = [fileIds];
      }
      fileIds = fileIds.map((id) => id.toString());
    }
    return Promise.resolve([...this.functions.values()]
      .filter((item) => {
        if (!fileIds) {
          return true;
        }
        return fileIds.indexOf(item.fileId.toString()) >= 0;
      }));
  }

  async getFileTests(fileId) {
    return Promise.resolve([...this.tests.values()]
      .filter((test) => test.fileId.toString() === fileId.toString()));
  }

  async getTestResultOverview() {
    const resultSet = Object.values(resultKind);
    const testResult = {
      manually: resultSet.reduce((obj, r) => {
        obj[r] = 0;
        return obj;
      }, {}),
      automatically: resultSet.reduce((obj, r) => {
        obj[r] = 0;
        return obj;
      }, {}),
    };
    [...this.tests.values()].forEach((test) => {
      if (test.kind === testKind.modified) {
        testResult.manually[test.resultKind] += 1;
      } else {
        testResult.automatically[test.resultKind] += 1;
      }
    });
    return Promise.resolve(testResult);
  }

  async getFilesOverview(versionId, suffix) {
    const filesOverview = {
      filesOverview: [],
      fileStatusCount: [],
    };
    const fileStatusCountMap = new Map();
    [
      statusKind.notCompiled,
      statusKind.compileFailed,
      statusKind.compiled,
      statusKind.waiting4compiling,
    ].forEach((fileStatusSet) => {
      fileStatusCountMap.set(fileStatusSet, { status: fileStatusSet, count: 0 });
    });
    [...this.files.values()].forEach((file) => {
      if (suffix.indexOf(file.extname) < 0) {
        return;
      }
      const tests = [...this.tests.values()]
        .filter((test) => test.fileId.toString() === file._id.toString());
      const extname = common.renameMongoKey(file.extname);
      const fileOverview = {
        _id: file._id,
        status: file.status,
        numberOfLines: file.numberOfLines,
        fileName: file.fileName,
        path: file.path,
        statementCovered: file.statementCovered[extname] || 0,
        statementTotal: file.statementTotal[extname] || 0,
        branchCovered: file.branchCovered[extname] || 0,
        branchTotal: file.branchTotal[extname] || 0,
        conditionCovered: file.conditionCovered[extname] || 0,
        conditionTotal: file.conditionTotal[extname] || 0,
        decisionCovered: file.decisionCovered[extname] || 0,
        decisionTotal: file.decisionTotal[extname] || 0,
        mcdcCovered: file.mcdcCovered[extname] || 0,
        mcdcTotal: file.mcdcTotal[extname] || 0,
        functionCount: file.functionCount,
        testCount: tests.length,
      };
      if (this.version.versionType === versionType.integrationTest) {
        // eslint-disable-next-line max-len
        fileOverview.invokedFuncTotal = (file.invokedFuncTotal && file.invokedFuncTotal[extname]) || 0;
        // eslint-disable-next-line max-len
        fileOverview.invokedFuncCovered = (file.invokedFuncCovered && file.invokedFuncCovered[extname]) || 0;
        fileOverview.invokeTotal = (file.invokeTotal && file.invokeTotal[extname]) || 0;
        fileOverview.invokeCovered = (file.invokeCovered && file.invokeCovered[extname]) || 0;
      }
      filesOverview.filesOverview.push(fileOverview);
      if (fileStatusCountMap.has(file.status)) {
        const statusCount = fileStatusCountMap.get(file.status);
        statusCount.count += 1;
        fileStatusCountMap.set(file.status, statusCount);
      }
    });
    filesOverview.fileStatusCount = [...fileStatusCountMap.values()];
    return Promise.resolve(filesOverview);
  }

  async getFunctionsOverview() {
    const funcResultStatistic = {
      passed: 0,
      failed: 0,
      undefined: 0,
    };
    return Promise.resolve([...this.functions.values()].map((func) => {
      const file = this.files.get(func.fileId.toString());
      func.fileName = file.fileName;
      func.testCount = func.testcases.size;
      func.resultKind = funcsModel.resultKind.passed;
      if (func.testCount === 0) {
        func.resultKind = funcsModel.resultKind.undefined;
      } else {
        const undefinedSet = new Set([
          resultKind.unknown,
          resultKind.noResult,
        ]);
        const failedSet = new Set([
          resultKind.failed,
          resultKind.runtimeError,
          resultKind.timeout,
          resultKind.systemError,
          resultKind.instrumentError,
          resultKind.compileError,
        ]);
        func.tests.forEach((testcase) => {
          if (failedSet.has(testcase.resultKind)) {
            func.resultKind = funcsModel.resultKind.failed;
            return;
          }
          if (func.resultKind === funcsModel.resultKind.failed) {
            return;
          }
          if (undefinedSet.has(testcase.resultKind)) {
            func.resultKind = funcsModel.resultKind.undefined;
          }
        });
      }
      funcResultStatistic[func.resultKind] += 1;
      return func;
    }))
      .then((func) => [func, funcResultStatistic]);
  }

  async getFunctionCoverageDistribution() {
    const interval = 25;
    const functions = [...this.functions.values()];
    const summary = {};
    const coverageNames = [];
    Object.keys(coverageKind).forEach((coverageName) => {
      const coverage = coverageKind[coverageName];
      if (coverage === coverageKind.all) {
        return;
      }
      // eslint-disable-next-line no-bitwise
      if ((this.version.coverages & coverage) > 0) {
        coverageNames.push(coverageName);
      }
    });
    coverageNames.forEach((coverageName) => {
      const segment = Math.floor(100 / interval + 1);
      summary[coverageName] = { distribution: new Array(segment).fill(0) };
    });
    functions.forEach((func) => {
      coverageNames.forEach((coverageName) => {
        const covered = func[`${coverageName}Covered`];
        const total = func[`${coverageName}Total`];
        if (total) {
          const index = Math.floor((covered * 100) / total / interval);
          summary[coverageName].distribution[index] += 1;
        }
      });
    });
    return Promise.resolve(summary);
  }

  async getVersionOverview() {
    const sourceExts = Object.values(this.version.languages)
      .reduce((arr, item) => arr.concat(item.suffixes), []);
    const headerExts = this.version.headers.suffixes;
    const versionOverview = {
      sourceFileCount: 0,
      headerFileCount: 0,
      numberOfLines: 0,
      sourceNumberOfLines: 0,
      headerNumberOfLines: 0,
      testCount: this.tests.size,
      functionCount: this.functions.size,
      statementCovered: 0,
      statementTotal: 0,
      branchCovered: 0,
      branchTotal: 0,
      conditionCovered: 0,
      conditionTotal: 0,
      decisionCovered: 0,
      decisionTotal: 0,
      mcdcCovered: 0,
      mcdcTotal: 0,
      invokedFuncTotal: 0,
      invokedFuncCovered: 0,
      invokeTotal: 0,
      invokeCovered: 0,
      projectName: this.project ? this.project.projectName : '',
      projectId: this.version.projectId,
      _id: this.version._id,
      versionName: this.version.versionName,
    };
    [...this.files.values()].forEach((file) => {
      const fileOverCount = common.getCountOfFile(file);
      versionOverview.statementCovered += fileOverCount.statementCovered;
      versionOverview.statementTotal += fileOverCount.statementTotal;
      versionOverview.branchTotal += fileOverCount.branchTotal;
      versionOverview.branchCovered += fileOverCount.branchCovered;
      versionOverview.conditionTotal += fileOverCount.conditionTotal;
      versionOverview.conditionCovered += fileOverCount.conditionCovered;
      versionOverview.decisionCovered += fileOverCount.decisionCovered;
      versionOverview.decisionTotal += fileOverCount.decisionTotal;
      versionOverview.mcdcTotal += fileOverCount.mcdcTotal;
      versionOverview.mcdcCovered += fileOverCount.mcdcCovered;
      versionOverview.invokedFuncTotal += fileOverCount.invokedFuncTotal;
      versionOverview.invokedFuncCovered += fileOverCount.invokedFuncCovered;
      versionOverview.invokeTotal += fileOverCount.invokeTotal;
      versionOverview.invokeCovered += fileOverCount.invokeCovered;
      versionOverview.numberOfLines += file.numberOfLines;
      if (headerExts.includes(file.extname)) {
        versionOverview.headerFileCount += fileOverCount.fileCount;
        versionOverview.headerNumberOfLines += file.numberOfLines;
      } else if (sourceExts.includes(file.extname)) {
        versionOverview.sourceFileCount += fileOverCount.fileCount;
        versionOverview.sourceNumberOfLines += file.numberOfLines;
      }
    });
    return Promise.resolve(versionOverview);
  }

  async getFunctionCfg(func, includeCovered = true) {
    let funcCfg = await funcsModel.getFunctionCFG(func._id, null, false);
    if (!includeCovered || !func.testcasesCoveredFields || !utility.isObjectType(func.testcasesCoveredFields)) {
      return funcCfg;
    }
    Object.keys(func.testcasesCoveredFields).forEach((testId) => {
      funcCfg = funcsModel.mergeCfgCovered(funcCfg, func.testcasesCoveredFields[testId], testId);
    });
    return funcsModel.completeDecisionCases(funcCfg, this.version.mcdcKind);
  }
}

module.exports = TargetMachineDataAdapter;
